// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.

package cn.birdbaby.plugins.sync;

import cn.birdbaby.plugins.Executor;
import cn.birdbaby.plugins.annotator.*;
import cn.birdbaby.plugins.selector.PsiElementSelector;
import cn.birdbaby.plugins.selector.SelectorRegistry;
import cn.birdbaby.plugins.common.CommonUtils;
import cn.birdbaby.plugins.sync.support.CurrGroupUtils;
import cn.birdbaby.plugins.sync.support.TypeMapper;
import com.intellij.codeInsight.actions.ReformatFilesOptions;
import com.intellij.database.model.DasColumn;
import com.intellij.database.model.DasNamespace;
import com.intellij.database.model.DasTable;
import com.intellij.database.psi.DbDataSource;
import com.intellij.database.psi.DbPsiFacade;
import com.intellij.database.types.DasType;
import com.intellij.database.util.DasUtil;
import com.intellij.formatting.FormattingModelBuilder;
import com.intellij.ide.lightEdit.LightEditCompatible;
import com.intellij.lang.LanguageFormatting;
import com.intellij.openapi.actionSystem.*;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.module.ModuleUtil;
import com.intellij.openapi.project.DumbAware;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Conditions;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.*;
import com.intellij.psi.search.FilenameIndex;
import com.intellij.psi.search.GlobalSearchScope;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.util.ArrayUtil;
import com.intellij.util.containers.JBIterable;
import org.jetbrains.annotations.NotNull;
import org.yaml.snakeyaml.Yaml;

import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class BBSyncAction extends AnAction implements DumbAware, LightEditCompatible {
    private static final Logger LOG = Logger.getInstance(BBSyncAction.class);

    private static ReformatFilesOptions myTestOptions;

    public BBSyncAction() {
        setEnabledInModalContext(true);
    }


    private static boolean isActionAvailable(@NotNull AnActionEvent event) {
        DataContext dataContext = event.getDataContext();
        Project project = CommonDataKeys.PROJECT.getData(dataContext);
        if (project == null) {
            return false;
        }

        Editor editor = CommonDataKeys.EDITOR.getData(dataContext);

        final VirtualFile[] files = CommonDataKeys.VIRTUAL_FILE_ARRAY.getData(dataContext);

        if (editor != null) {
            PsiFile file = PsiDocumentManager.getInstance(project).getPsiFile(editor.getDocument());
            if (file == null || file.getVirtualFile() == null) {
                return false;
            }

            if (LanguageFormatting.INSTANCE.forContext(file) != null) {
                return true;
            }
        } else if (files != null && containsOnlyFiles(files)) {
            boolean anyFormatters = false;
            for (VirtualFile virtualFile : files) {
                final PsiFile psiFile = PsiManager.getInstance(project).findFile(virtualFile);
                if (psiFile == null) {
                    return false;
                }
                final FormattingModelBuilder builder = LanguageFormatting.INSTANCE.forContext(psiFile);
                if (builder != null) {
                    anyFormatters = true;
                    break;
                }
            }
            if (!anyFormatters) {
                return false;
            }
        } else if (files != null && files.length == 1) {
            // skip. Both directories and single files are supported.
        } else if (LangDataKeys.MODULE_CONTEXT.getData(dataContext) == null &&
                PlatformCoreDataKeys.PROJECT_CONTEXT.getData(dataContext) == null) {
            PsiElement element = CommonDataKeys.PSI_ELEMENT.getData(dataContext);
            if (element == null) {
                return false;
            }
            if (!(element instanceof PsiDirectory)) {
                PsiFile file = element.getContainingFile();
                if (file == null || LanguageFormatting.INSTANCE.forContext(file) == null) {
                    return false;
                }
            }
        }
        return true;
    }


    static boolean containsOnlyFiles(VirtualFile @NotNull [] files) {
        if (files.length < 1) return false;
        for (VirtualFile virtualFile : files) {
            if (virtualFile.isDirectory()) return false;
        }
        return true;
    }

    @Override
    public void actionPerformed(@NotNull AnActionEvent event) {
        dasTableMap=new HashMap<>();
        DataContext dataContext = event.getDataContext();
        final Project project = CommonDataKeys.PROJECT.getData(dataContext);
        if (project == null) {
            return;
        }
        final Editor editor = CommonDataKeys.EDITOR.getData(dataContext);
        final VirtualFile[] files = CommonDataKeys.VIRTUAL_FILE_ARRAY.getData(dataContext);

        PsiFile file = null;
        PsiDirectory dir;

        //打开编辑器了，直接操作当前编辑器的文件
        if (editor != null) {
            file = PsiDocumentManager.getInstance(project).getPsiFile(editor.getDocument());
            if (file == null) return;
            dir = file.getContainingDirectory();
        } else {
            PsiElement element = CommonDataKeys.PSI_ELEMENT.getData(dataContext);
            if (element == null) return;
            if (element instanceof PsiDirectoryContainer) {
                dir = ArrayUtil.getFirstElement(((PsiDirectoryContainer) element).getDirectories());
            } else if (element instanceof PsiDirectory) {
                dir = (PsiDirectory) element;
            } else {
                file = element.getContainingFile();
                if (file == null) return;
                dir = file.getContainingDirectory();
            }
        }
        if (file == null && dir != null) {
            //清空缓冲区，减少并发修改产生的冲突
            PsiDocumentManager.getInstance(project).commitAllDocuments();
            PsiElementSelector selector = SelectorRegistry.find("FieldDesc");
            Map<PsiJavaFile, List<PsiElement>> psiElements = selector.selectPsiElement(dir);
            List<AbstractAnnotator> annotators = Arrays.asList(AnnotatorRegistry.find("FieldDesc"));

            Executor.excuteAnnotate(project, psiElements, annotators, context -> {
                Executor.importClassIfNotImported(context, project, "context.getParentPackageName()" + ".infrastruction.constant.Constants");
            });
        } else {
            //清缓存
            String schemaName=getSchema(project,file);
            DasNamespace dasNamespace=getDasNamespace(project,schemaName);
            List<DbDataSource> dataSources = DbPsiFacade.getInstance(project).getDataSources();
            for(DbDataSource dbDataSource:dataSources){
                JBIterable<? extends DasTable> tables = DasUtil.getTables(dbDataSource);
                tables.forEach(t->dasTableMap.put(t.getName(),t));
            }
            DasTable a=dasTableMap.get("camera");
            JBIterable<? extends DasColumn> columns = DasUtil.getColumns(a);
            columns.forEach(column->{
                String name = column.getName();
                DasType dasType = column.getDasType();

            });
            List<TypeMapper> elementList = CurrGroupUtils.getCurrTypeMapperGroup().getElementList();
            System.out.println(elementList);
            System.out.println(dasNamespace);
            String tableName=getTableName(file);
            System.out.println(tableName);
            createField(project,file,"Long","idasas");
            PsiDocumentManager.getInstance(project).commitAllDocuments();
            //最后才会跑进这里来
//            new BBAddFieldDescProcessor(project, editor, file).processCode();
        }
    }

    //字段应该加到什么位置上面？默认加载当前最后一个字段后面；
    private void createField(Project project,PsiFile psiFile,String javaType,String fieldName){
        CommonUtils.processWithGroupId(project,UUID.randomUUID().toString(),()->{
            PsiField privateLongId = JavaPsiFacade.getInstance(project).getElementFactory().createFieldFromText(String.format("private %s %s;",javaType,fieldName), psiFile);

            PsiMethod psiMethod=PsiTreeUtil.findChildOfType(psiFile,PsiMethod.class);
            PsiElement copy = psiMethod.copy();
            PsiCodeBlock codeBlock=PsiTreeUtil.findChildOfType(copy,PsiCodeBlock.class);
            codeBlock.delete();

            Collection<PsiAnnotation> annotations = PsiTreeUtil.findChildrenOfAnyType(copy, PsiAnnotation.class);
            for(PsiAnnotation annotation:annotations){
                String name=annotation.getNameReferenceElement().getReferenceName();
                if("ManyToMany".contains(name)){
                    annotation.delete();
                }
            }

            PsiField psiField=PsiTreeUtil.findChildOfType(psiFile,PsiField.class);
            PsiAnnotation psiAnnotation=PsiTreeUtil.findChildOfType(psiField,PsiAnnotation.class);
            System.out.println(psiAnnotation);
            Collection<PsiJavaToken> javaTokens = PsiTreeUtil.findChildrenOfAnyType(psiFile, PsiJavaToken.class);
            javaTokens.stream().sorted(Comparator.comparingInt(token -> token.getTextOffset()));
            PsiJavaToken javaToken = javaTokens.stream().findFirst().get();
            PsiClass psiClass = PsiTreeUtil.findChildOfType(psiFile, PsiClass.class);
            System.out.println(psiClass.getName());
            psiFile.addBefore(privateLongId,psiField);
            psiFile.addBefore(copy,psiField);
        });

    }

    public String getTableName(PsiFile psiFile){
        Collection<PsiAnnotation> childrenOfAnyType = PsiTreeUtil.findChildrenOfAnyType(psiFile, PsiAnnotation.class);
        if(childrenOfAnyType.isEmpty()) return "";
        for(PsiAnnotation annotation:childrenOfAnyType){
            String name=annotation.getNameReferenceElement().getReferenceName();
            if(name.equalsIgnoreCase("table")){
                PsiAnnotationMemberValue attributeValue = annotation.findAttributeValue("name");
                return attributeValue.getText();
            }
        }
        return "";
    }

    //全部表的dasTable都放内存里面。经测试crm项目3000个表，才用了130K。意料之外的小
    private Map<String,DasTable> dasTableMap;
    //
    private DasTable getTable(DbDataSource dataSource,String tableName) {
        return dataSource.getModel().traverser().expandAndSkip(Conditions.instanceOf(DasNamespace.class)).filter(DasTable.class).find(t->t.getName().equalsIgnoreCase(tableName));
    }

    public DasNamespace getDasNamespace(Project project,String schemaName){

        List<DbDataSource> dataSources = DbPsiFacade.getInstance(project).getDataSources();
        for (DbDataSource dbDataSource : dataSources) {
            JBIterable<? extends DasNamespace> schemas = DasUtil.getSchemas(dbDataSource);
            DasNamespace dasNamespace = schemas.find(s -> s.getName().equals(schemaName));
            if(dasNamespace!=null)return dasNamespace;
        }
        throw new RuntimeException("找不到这个实体对应的数据库配置");

    }
    public String getSchema(Project project, PsiFile file)  {
        //根据文件中的schema注解确定
        //根据application.yml的配置确定
        Module moduleForFile = ModuleUtil.findModuleForFile(file);
        Collection<VirtualFile> virtualFilesByName = FilenameIndex.getVirtualFilesByName("application.yml", GlobalSearchScope.moduleScope(moduleForFile));
        String dataSource = "";
        for (VirtualFile application : virtualFilesByName) {
            try {
                Map<String, Object> map = new Yaml().load(application.getInputStream());
                String url = getValueFromMap(map, "url");
                Pattern pattern = Pattern.compile("/[^/?]+/([^?]+)");
                Matcher matcher = pattern.matcher(url);

                if (matcher.find()) {
                    String dbName = matcher.group(1);
                    return dbName;
                } else {
                    System.out.println("Database name not found.");
                }
            }catch (Exception e){
                e.printStackTrace();
            }
        }
        //todo 如果application.yml里面没有，弹出对话框让用户选择

        return dataSource;
    }

    public String getValueFromMap(Map map, String key) {
        if (map.containsKey(key)) return (String) map.get(key);
        String value = "";
        for (Object entry : map.entrySet()) {
            Map.Entry en = (Map.Entry) entry;
            if (en.getValue() instanceof Map) {
                value = getValueFromMap((Map) en.getValue(), key);
                if (!Objects.isNull(value)) {
                    break;
                }
            }
        }
        return value;
    }

    @Override
    public @NotNull ActionUpdateThread getActionUpdateThread() {
        return ActionUpdateThread.BGT;
    }

    @Override
    public void update(@NotNull AnActionEvent event) {
        Presentation presentation = event.getPresentation();
        boolean available = isActionAvailable(event);
        if (event.isFromContextMenu()) {
            presentation.setEnabledAndVisible(available);
        } else {
            presentation.setEnabled(available);
        }
    }
}